﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.SessionState;
using System.Web.UI.WebControls;
using SmartParking.Data;
using SmartParking.Data.Entities;

namespace SmartParking.Bussiness
{
    public static class SecurityManager
    {
        #region Managers

        private const string ManagersTableName = "Managers";
        private const int SysAdminManagerId = 1;
        private const int WebAdminManagerId = 2;

        /// <summary>
        /// Return all managers
        /// </summary>
        public static List<Manager> GetManagers()
        {
            using (var context = new SmartParkingContext())
            {
                return context.Managers.Where(item => !item.Deleted).ToList();
            }
        }

        /// <summary>
        /// Return manager by id
        /// </summary>
        public static Manager GetManager(int id, SmartParkingContext context = null)
        {
            var innerContext = context ?? new SmartParkingContext();
            try
            {
                return innerContext.Managers.SingleOrDefault(item => item.ManagerID == id && !item.Deleted);
            }
            finally
            {
                if (context == null) innerContext.Dispose();
            }
        }

        /// <summary>
        /// Return manager by user and password
        /// </summary>
        public static Manager GetManager(string userName, string password, SmartParkingContext context = null)
        {
            var innerContext = context ?? new SmartParkingContext();
            try
            {
                //TODO: Encrypt/Decrypt password
                return innerContext.Managers.SingleOrDefault(item => item.UserName == userName && item.Password == password && !item.Deleted);
            }
            finally
            {
                if (context == null) innerContext.Dispose();
            }
        }

        /// <summary>
        /// Bind all managers into a grid
        /// </summary>
        public static void BindManagers(ref ADEVSO.Web.UI.WebControls.GridView grid)
        {
            grid.DataSource = GetManagers();
            grid.DataBind();
        }

        /// <summary>
        /// Return true if is a system manager
        /// </summary>
        public static bool IsSystemManager(int id)
        {
            Manager row = GetManager(id);
            if (row.Role.Name == SmartParkingRoles.SystemManager)
                return true;
            return false;
        }

        /// <summary>
        /// Verify if user has access to all customers
        /// </summary>
        public static bool HaveAccessToAllCustomers(string user)
        {
            if (!System.Web.Security.Roles.IsUserInRole(user, SmartParkingRoles.SystemManager) && !System.Web.Security.Roles.IsUserInRole(user, SmartParkingRoles.Web))
                return false;
            return true;
        }

        /// <summary>
        /// Add new manager
        /// </summary>
        private static void AddManager(int customerId, int roleId, string userName, string password, string email, string phone, int managerId, string ipAddress)
        {
            using (var context = new SmartParkingContext())
            {
                var row = new Manager
                    {
                        CustomerID = customerId,
                        RoleID = roleId,
                        UserName = userName,
                        Password = password,
                        Email = email,
                        Phone = phone
                    };
                context.Managers.Add(row);
                LogManager.Log(managerId, LogActions.INSERT, ipAddress, ManagersTableName, row.ManagerID.ToString());
            }
        }

        /// <summary>
        /// Update a manager
        /// </summary>
        private static void UpdateManager(int managerId, int roleId, string userName, string password, string email, string phone, int managerIdd, string ipAddress)
        {
            using (var context = new SmartParkingContext())
            {
                var row = GetManager(managerId, context);
                row.RoleID = roleId;
                row.UserName = userName;
                row.Password = password;
                row.Email = email;
                row.Phone = phone;
                context.SaveChanges();
                LogManager.Log(managerIdd, LogActions.UPDATE, ipAddress, ManagersTableName, row.ManagerID.ToString());
            }
        }

        /// <summary>
        /// Save a manager
        /// </summary>
        public static void SaveManager(string managerId, int customerId, int roleId, string userName, string password, string email, string phone, int managerIdd, string ipAddress)
        {
            if (String.IsNullOrEmpty(managerId))
                AddManager(customerId, roleId, userName, password, email, phone, managerIdd, ipAddress);
            else
                UpdateManager(Convert.ToInt32(managerId), roleId, userName, password, email, phone, managerIdd, ipAddress);
        }

        /// <summary>
        /// Delete a manager
        /// </summary>
        public static void DeleteManager(int managerId, int managerIdd, string ipAddress)
        {
            using (var context = new SmartParkingContext())
            {
                var row = GetManager(managerId, context);
                row.Deleted = true;
                context.SaveChanges();
                LogManager.Log(managerIdd, LogActions.DELETE, ipAddress, ManagersTableName, row.CustomerID.ToString());
            }
        }

        #endregion

        #region Users

        private const string UsersTableName = "Users";

        /// <summary>
        /// Return all users
        /// </summary>
        public static List<User> GetUsers(Func<User, bool> conditions = null)
        {
            using (var context = new SmartParkingContext())
            {
                if (conditions == null) conditions = item => !item.Deleted;
                else conditions += item => !item.Deleted;
                return context.Users.Where(conditions).ToList();
            }
        }

        /// <summary>
        /// Return user by id
        /// </summary>
        public static User GetUser(long id, SmartParkingContext context = null)
        {
            var innerContext = context ?? new SmartParkingContext();
            try
            {
                return innerContext.Users.SingleOrDefault(item => item.UserID == id && !item.Deleted);
            }
            finally
            {
                if (context == null) innerContext.Dispose();
            }
        }

        /// <summary>
        /// Return user by email
        /// </summary>
        public static User GetUser(string email, SmartParkingContext context = null)
        {
            var innerContext = context ?? new SmartParkingContext();
            try
            {
                //TODO: Encrypt/Decrypt password
                return innerContext.Users.SingleOrDefault(item => item.Email == email && !item.Deleted);
            }
            finally
            {
                if (context == null) innerContext.Dispose();
            }
        }

        /// <summary>
        /// Return user by email and password
        /// </summary>
        public static User GetUser(string email, string password, SmartParkingContext context = null)
        {
            var innerContext = context ?? new SmartParkingContext();
            try
            {
                //TODO: Encrypt/Decrypt password
                return innerContext.Users.SingleOrDefault(item => item.Email == email && item.Password == password && !item.Deleted);
            }
            finally
            {
                if (context == null) innerContext.Dispose();
            }
        }

        /// <summary>
        /// Bind all users into a grid
        /// </summary>
        public static void BindUsers(ref GridView grid)
        {
            grid.DataSource = GetUsers();
            grid.DataBind();
        }

        /// <summary>
        /// Add new user
        /// </summary>
        private static void AddUser(string email, string name, string lastName, string password, bool receiveInfo, int managerId, string ipAddress)
        {
            using (var context = new SmartParkingContext())
            {
                var row = new User
                {
                    Email = email,
                    Name = name,
                    LastName = lastName,
                    Password = password,
                    ReceiveInfo = receiveInfo
                };
                context.Users.Add(row);
                context.SaveChanges();
                LogManager.Log(managerId, LogActions.INSERT, ipAddress, UsersTableName, row.UserID.ToString());
            }
        }

        /// <summary>
        /// Update a user
        /// </summary>
        private static void UpdateUser(long userId, string email, string name, string lastName, string password, bool receiveInfo, int managerId, string ipAddress)
        {
            using (var context = new SmartParkingContext())
            {
                var row = GetUser(userId, context);
                row.Email = email;
                row.Name = name;
                row.LastName = lastName;
                row.Password = password;
                row.ReceiveInfo = receiveInfo;
                context.SaveChanges();
                LogManager.Log(managerId, LogActions.UPDATE, ipAddress, UsersTableName, row.UserID.ToString());
            }
        }

        /// <summary>
        /// Save a user
        /// </summary>
        public static void SaveUser(int userId, string email, string name, string lastName, string password, bool receiveInfo, int managerId, string ipAddress)
        {
            if (String.IsNullOrEmpty(email))
                AddUser(email, name, lastName, password, receiveInfo, managerId, ipAddress);
            else
                UpdateUser(Convert.ToInt32(userId), email, name, lastName, password, receiveInfo, managerId, ipAddress);
        }

        /// <summary>
        /// Delete a user
        /// </summary>
        public static void DeleteUser(int userId, int managerId, string ipAddress)
        {
            using (var context = new SmartParkingContext())
            {
                var row = GetUser(userId, context);
                row.Deleted = true;
                context.SaveChanges();
                LogManager.Log(managerId, LogActions.DELETE, ipAddress, UsersTableName, row.UserID.ToString());
            }
        }

        /// <summary>
        /// Verify if order belongs to user
        /// </summary>
        public static bool VerifyUserOrder(string email, string orderId)
        {
            var user = GetUser(email);
            var orders = ReservationsManager.GetOrders(user.UserID);
            var result = orders.Any(item => item.OrderID.Trim() == orderId.Trim());
            return result;
        }

        #endregion

        #region Roles

        private const string RolesTableName = "Roles";

        /// <summary>
        /// Return all roles
        /// </summary>
        public static List<Role> GetRoles()
        {
            using (var context = new SmartParkingContext())
            {
                return context.Roles.Where(item => !item.Deleted).ToList();
            }
        }

        /// <summary>
        /// Return role by id
        /// </summary>
        public static Role GetRole(int id, SmartParkingContext context = null)
        {
            var innerContext = context ?? new SmartParkingContext();
            try
            {
                return innerContext.Roles.SingleOrDefault(item => item.RoleID == id && !item.Deleted);
            }
            finally
            {
                if (context == null) innerContext.Dispose();
            }
        }

        /// <summary>
        /// Return all roles names
        /// </summary>
        private static List<String> GetRolesNames()
        {
            var roles = GetRoles();
            return roles.Select(role => role.Name.Trim()).ToList();
        }

        /// <summary>
        /// Bind all roles into a drop down list
        /// </summary>
        public static void BindRoles(ref DropDownList list)
        {
            list.DataSource = GetRoles();
            list.DataTextField = "Name";
            list.DataValueField = "RoleID";
            list.DataBind();
        }

        /// <summary>
        /// Bind all roles into a grid
        /// </summary>
        public static void BindRoles(ref ADEVSO.Web.UI.WebControls.GridView grid)
        {
            grid.DataSource = GetRoles();
            grid.DataBind();
        }

        /// <summary>
        /// Add new role
        /// </summary>
        private static void AddRole(string name, int managerId, string ipAddress)
        {
            using (var context = new SmartParkingContext())
            {
                var row = new Role
                    {
                        Name = name
                    };
                context.Roles.Add(row);
                context.SaveChanges();
                LogManager.Log(managerId, LogActions.INSERT, ipAddress, RolesTableName, row.RoleID.ToString());
            }
        }

        /// <summary>
        /// Update a role
        /// </summary>
        private static void UpdateRole(int roleId, string name, int managerId, string ipAddress)
        {
            using (var context = new SmartParkingContext())
            {
                var row = GetRole(roleId, context);
                row.Name = name;
                context.SaveChanges();
                LogManager.Log(managerId, LogActions.UPDATE, ipAddress, RolesTableName, row.RoleID.ToString());
            }
        }

        /// <summary>
        /// Save a role
        /// </summary>
        public static void SaveRole(string roleId, string name, int managerId, string ipAddress)
        {
            if (String.IsNullOrEmpty(roleId))
                AddRole(name, managerId, ipAddress);
            else
                UpdateRole(Convert.ToInt32(roleId), name, managerId, ipAddress);
        }

        /// <summary>
        /// Delete a role
        /// </summary>
        public static void DeleteRole(int roleId, int managerId, string ipAddress)
        {
            using (var context = new SmartParkingContext())
            {
                var row = GetRole(roleId, context);
                row.Deleted = true;
                context.SaveChanges();
                LogManager.Log(managerId, LogActions.DELETE, ipAddress, RolesTableName, row.RoleID.ToString());
            }
        }

        #endregion

        #region Authentication

        /// <summary>
        /// Login process for a manager
        /// </summary>
        public static bool LoginManager(HttpContextBase httpContext, string userName, string password)
        {
            bool result = false;
            Manager manager = null;
            try
            {
                manager = GetManager(userName, password);
                if (manager != null)
                {
                    var loginRole = manager.Role.RolePermissions.Where(role => role.Permission.Name.Trim() == SmartParkingPermissions.Login);
                    if (loginRole.Any() && Membership.ValidateUser(userName, password))
                    {
                        FormsAuthentication.SetAuthCookie(userName, true);
                        result = true;
                    }
                    LogManager.Log(manager.ManagerID, LogActions.SELECT, httpContext.Request.UserHostAddress, ManagersTableName, manager.ManagerID.ToString());
                }
            }
            catch
            {
                manager = null;
            }            
            httpContext.Session["Manager"] = manager;
            return result;
        }

        /// <summary>
        /// Save an user
        /// </summary>
        public static bool SaveUser(HttpContextBase httpContext, string email, string name, string lastName, string password, bool receiveInfo, long userId = -1)
        {
            var result = false;
            try
            {
                if (userId == -1)
                {
                    RegisterUser(httpContext, email, name, lastName, password, receiveInfo);
                }
                else
                {
                    UpdateUser(userId, email, name, lastName, password, receiveInfo, WebAdminManagerId, httpContext.Request.UserHostAddress);
                }
                result = true;
            }
            catch
            {
            }
            return result;
        }

        /// <summary>
        /// Register process for a user
        /// </summary>
        private static bool RegisterUser(HttpContextBase httpContext, string email, string name, string lastName, string password, bool receiveInfo)
        {
            bool result = false;
            User user = null;
            try
            {
                AddUser(email, name, lastName, password, receiveInfo, WebAdminManagerId, httpContext.Request.UserHostAddress);
                user = GetUser(email, password);
                if (user != null)
                {
                    // Attempt to register the user
                    MembershipCreateStatus createStatus;
                    Membership.CreateUser(email, password, email, passwordQuestion: null, passwordAnswer: null, isApproved: true, providerUserKey: null, status: out createStatus);
                    if (createStatus == MembershipCreateStatus.Success)
                    {
                        FormsAuthentication.SetAuthCookie(email, createPersistentCookie: false);
                        result = true;
                    }
                }
            }
            catch
            {
                user = null;
            }
            return result;
        }

        /// <summary>
        /// Login process for a user
        /// </summary>
        public static bool LoginUser(HttpContextBase httpContext, string email, string password, bool rememberMe)
        {
            bool result = false;
            User user = null;
            try
            {
                user = GetUser(email, password);
                if (user != null && Membership.ValidateUser(email, password))
                {
                    FormsAuthentication.SetAuthCookie(email, rememberMe);
                    result = true;
                    LogManager.Log(WebAdminManagerId, LogActions.SELECT, httpContext.Request.UserHostAddress, UsersTableName, user.UserID.ToString());
                }
            }
            catch
            {
                user = null;
            }
            return result;
        }

        /// <summary>
        /// Refresh user roles
        /// </summary>
        public static void RefreshUserRoles(this Manager manager)
        {
            string userName = manager.UserName.Trim();
            if (System.Web.Security.Roles.GetRolesForUser(userName).Length > 0)
                System.Web.Security.Roles.RemoveUserFromRoles(userName, System.Web.Security.Roles.GetRolesForUser(userName));
            System.Web.Security.Roles.AddUserToRole(userName, manager.Role.Name.Trim());
            System.Web.Security.Roles.AddUserToRoles(userName, manager.GetRolePermissions());
        }

        /// <summary>
        /// Refresh roles
        /// </summary>
        public static List<String> RefreshRoles(int managerId, string ipAddress)
        {
            try
            {
                System.Web.Security.Roles.DeleteCookie();

                // Load SmartParking roles
                List<String> roles = GetRolesNames();
                // Load SmartParking permissions
                roles.AddRange(GetPermissionsNames());

                // Add new roles
                foreach (String rol in roles)
                    if (!System.Web.Security.Roles.RoleExists(rol))
                        System.Web.Security.Roles.CreateRole(rol);

                // Delete old roles that not longer exists
                String[] roles2 = System.Web.Security.Roles.GetAllRoles();
                foreach (String rol in roles2)
                    if (!roles.Contains(rol))
                        System.Web.Security.Roles.DeleteRole(rol);

                return roles;
            }
            catch (Exception e)
            {
                //throw e;
                return null;
            }
            finally
            {
            }
        }

        /// <summary>
        /// Remove user roles
        /// </summary>
        public static void RemoveUserRoles(string user, params string[] roles)
        {
            foreach (string role in roles)
                System.Web.Security.Roles.RemoveUserFromRole(user, role);
        }

        /// <summary>
        /// Add user roles
        /// </summary>
        public static void AddUserRoles(string user, params string[] roles)
        {
            foreach (string role in roles)
                if (! System.Web.Security.Roles.IsUserInRole(role))
                    System.Web.Security.Roles.AddUserToRole(user, role);
        }

        /// <summary>
        /// Reload user roles
        /// </summary>
        public static void ReloadUserRoles(string user, string[] newRoles)
        {
            string[] oldRoles = System.Web.Security.Roles.GetRolesForUser(user);
            // Delete old roles that not longer the user have
            foreach (string oldRole in oldRoles)
                System.Web.Security.Roles.RemoveUserFromRole(user, oldRole);
            // Add new roles added to user
            foreach (string newRole in oldRoles)
                System.Web.Security.Roles.AddUserToRole(user, newRole);
        }

        /// <summary>
        /// Create new role
        /// </summary>
        public static Role New(HttpSessionState session, String ipAddress, Int32 managerId)
        {
            //Connection connection = session["Connection"] as Connection;
            //return new Role(connection, ipAddress, managerID);
            return null;
        }

        #endregion

        #region Permissions

        private const string PermissionsTableName = "Permissions";

        /// <summary>
        /// Return all permissions
        /// </summary>
        public static List<Permission> GetPermissions()
        {
            using (var context = new SmartParkingContext())
            {
                return context.Permissions.ToList();
            }
        }

        /// <summary>
        /// Return all permissions assigned to a role
        /// </summary>
        public static List<Permission> GetAssignedPermissions(int roleId)
        {
            var permissions =
                from permission in GetPermissions()
                let rolePermissionIDs = from rolePermission in GetRole(roleId).RolePermissions select rolePermission.PermissionID
                where rolePermissionIDs.Contains(permission.PermissionID)
                select permission;
            return permissions.ToList();
        }

        /// <summary>
        /// Return all permissions available for a role
        /// </summary>
        public static List<Permission> GetAvailablePermissions(int roleId)
        {
            var permissions =
                from permission in GetPermissions()
                let rolePermissionIDs = from rolePermission in GetRole(roleId).RolePermissions select rolePermission.PermissionID
                where ! rolePermissionIDs.Contains(permission.PermissionID)
                select permission;
            return permissions.ToList();
        }

        /// <summary>
        /// Return permission by id
        /// </summary>
        public static Permission GetPermission(int id, SmartParkingContext context = null)
        {
            var innerContext = context ?? new SmartParkingContext();
            try
            {
                return innerContext.Permissions.SingleOrDefault(item => item.PermissionID == id);
            }
            finally
            {
                if (context == null) innerContext.Dispose();
            }
        }

        /// <summary>
        /// Return permission by name
        /// </summary>
        public static Permission GetPermission(string name, SmartParkingContext context = null)
        {
            var innerContext = context ?? new SmartParkingContext();
            try
            {
                return innerContext.Permissions.SingleOrDefault(item => item.Name.Trim() == name.Trim());
            }
            finally
            {
                if (context == null) innerContext.Dispose();
            }
        }

        /// <summary>
        /// Return all permissions names
        /// </summary>
        private static List<String> GetPermissionsNames()
        {
            var permissions = GetPermissions();
            return permissions.Select(permission => permission.Name.Trim()).ToList();
        }

        /// <summary>
        /// Bind all permissions into a grid
        /// </summary>
        public static void BindPermissions(ref ADEVSO.Web.UI.WebControls.GridView grid)
        {
            grid.DataSource = GetPermissions();
            grid.DataBind();
        }

        /// <summary>
        /// Bind all permissions assigned to a role into a listbox
        /// </summary>
        public static void BindAssignedPermissions(ref ListBox list, int roleId)
        {
            list.DataSource = GetAssignedPermissions(roleId);
            list.DataTextField = "Name";
            list.DataValueField = "PermissionID";
            list.DataBind();
        }

        /// <summary>
        /// Bind all permissions available for a role into a listbox
        /// </summary>
        public static void BindAvailablePermissions(ref ListBox list, int roleId)
        {
            list.DataSource = GetAvailablePermissions(roleId);
            list.DataTextField = "Name";
            list.DataValueField = "PermissionID";
            list.DataBind();
        }

        /// <summary>
        /// Add new permission
        /// </summary>
        private static void AddPermission(string name, int managerId, string ipAddress)
        {
            using (var context = new SmartParkingContext())
            {
                var row = new Permission
                {
                    Name = name
                };
                context.Permissions.Add(row);
                context.SaveChanges();
                LogManager.Log(managerId, LogActions.INSERT, ipAddress, PermissionsTableName, row.PermissionID.ToString());
            }
        }

        /// <summary>
        /// Update a permission
        /// </summary>
        private static void UpdatePermission(int permissionId, string name, int managerId, string ipAddress)
        {
            using (var context = new SmartParkingContext())
            {
                var row = GetPermission(permissionId, context);
                row.Name = name;
                context.SaveChanges();
                LogManager.Log(managerId, LogActions.UPDATE, ipAddress, PermissionsTableName, row.PermissionID.ToString());
            }
        }

        /// <summary>
        /// Save a permission
        /// </summary>
        public static void SavePermission(string permissionId, string name, int managerId, string ipAddress)
        {
            if (String.IsNullOrEmpty(permissionId))
                AddPermission(name, managerId, ipAddress);
            else
                UpdatePermission(Convert.ToInt32(permissionId), name, managerId, ipAddress);
        }

        /// <summary>
        /// Delete a permission
        /// </summary>
        public static void DeletePermission(int permissionId, int managerId, string ipAddress)
        {
            using (var context = new SmartParkingContext())
            {
                var row = GetPermission(permissionId, context);
                row.Deleted = true;
                context.SaveChanges();
                LogManager.Log(managerId, LogActions.DELETE, ipAddress, PermissionsTableName, row.PermissionID.ToString());
            }
        }

        #endregion

        #region RolePermissions

        private const string RolePermissionsTableName = "RolePermissions";

        /// <summary>
        /// Return role permissions by manager
        /// </summary>
        public static string[] GetRolePermissions(this Manager manager)
        {
            return manager.Role.RolePermissions.Select(item => item.Permission.Name.Trim()).ToArray();
        }

        /// <summary>
        /// Return role permission by id
        /// </summary>
        public static RolePermission GetRolePermission(int roleId, int permissionId, SmartParkingContext context = null)
        {
            var innerContext = context ?? new SmartParkingContext();
            try
            {
                return innerContext.RolePermissions.SingleOrDefault(item => item.RoleID == roleId && item.PermissionID == permissionId);
            }
            finally
            {
                if (context == null) innerContext.Dispose();
            }
        }

        /// <summary>
        /// Add a role permission
        /// </summary>
        public static void AddRolePermission(int roleId, int permissionId, int managerId, string ipAddress)
        {
            using (var context = new SmartParkingContext())
            {
                var row = new RolePermission
                {
                    RoleID = roleId,
                    PermissionID = permissionId
                };
                context.RolePermissions.Add(row);
                context.SaveChanges();
                LogManager.Log(managerId, LogActions.INSERT, ipAddress, RolePermissionsTableName, row.RoleID.ToString() + "-" + row.PermissionID.ToString());
            }
        }

        /// <summary>
        /// Delete a role permission
        /// </summary>
        public static void DeleteRolePermission(int roleId, int permissionId, int managerId, string ipAddress)
        {
            using (var context = new SmartParkingContext())
            {
                var row = GetRolePermission(roleId, permissionId, context);
                context.RolePermissions.Remove(row);
                context.SaveChanges();
                LogManager.Log(managerId, LogActions.DELETE, ipAddress, RolePermissionsTableName, row.RoleID.ToString() + "-" + row.PermissionID.ToString());
            }
        }

        #endregion
    }

    public static class SmartParkingRoles
    {
        public static string SystemManager = "System Manager";
        public static string Web = "Web";
    }

    public static class SmartParkingPermissions
    {
        public static string Login = "Login";
        public static string RolesMaintenance = "Roles Maintenance";
        public static string PermissionsMaintenance = "Permissions Maintenance";
        public static string ManagersMaintenance = "Managers Maintenance";
        public static string ParkingsMaintenance = "Parkings Maintenance";
        public static string ParkingZonesMaintenance = "ParkingZones Maintenance";
        public static string ParkingZoneSpacesMaintenance = "ParkingZoneSpaces Maintenance";
        public static string UsersMaintenance = "Users Maintenance";
        public static string ReservationsMaintenance = "Reservations Maintenance";
        public static string TransactionsMaintenance = "Transactions Maintenance";
        public static string MakeReservations = "Make Reservations";
    }
}